package markdown

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
)

const (
	CodeParam           ParamTypePosition = "code"
	ResponseParam       ParamTypePosition = "response"
	ResponseHeaderParam ParamTypePosition = "response-header"
	// 请求类型参数
	QueryRequestParam    ParamTypePosition = "query"
	HeaderRequestParam   ParamTypePosition = "header"
	PathRequestParam     ParamTypePosition = "path"
	CookieRequestParam   ParamTypePosition = "cookie"
	FormDataRequestParam ParamTypePosition = "formData"
	BodyRequestParam     ParamTypePosition = "body"
	// BodyParam body类型参数
	BodyParam ParamTypePosition = "body-param"
)

type ParamTypePosition string

type ParamParser struct {
	ParamTypePosition ParamTypePosition
	MediaType         string
	*resolver
}

func (p *ParamParser) newParam(param string, schema *SchemaOrItems, required []string) (paramInfo *ParamInfo) {
	paramInfo = new(ParamInfo)
	paramInfo.ChildParamSchemaInfo = make([]ParamInfo, 0)
	paramInfo.Enum = make([]interface{}, 0)
	// 参数名
	paramInfo.Name = param
	// 参数值定义、子参数获取
	paramInfo.Value, paramInfo.ChildParamSchemaInfo = p.parseParam(schema)
	// 类型定义
	if schema.Type != "" {
		paramInfo.Type = schema.Type
	}
	if schema.Schema != nil && schema.Schema.Type != "" {
		paramInfo.Type = schema.Schema.Type
	}
	if schema.Format != "" {
		paramInfo.Type = schema.Format
	}
	// 参数枚举
	paramInfo.Enum = schema.Enum
	// 参数默认值
	paramInfo.Default = schema.Default
	// 参数是否必须
	sort.Strings(required)
	index := sort.SearchStrings(required, param)
	paramInfo.Required = index < len(required) && required[index] == param
	// 参数描述
	paramInfo.Description = schema.Description
	if schema.Maximum > 0 {
		paramInfo.Description += "<br> **最大值:**"
		paramInfo.Description += fmt.Sprintf("`maximum:%v`", schema.Minimum)
	}
	if schema.Minimum > 0 {
		paramInfo.Description += "<br> **最小值:**"
		paramInfo.Description += fmt.Sprintf("`minimum:%v`", schema.Minimum)
	}
	if len(schema.Enum) != 0 {
		paramInfo.Description += "<br> **Supported parameters:**"
		for _, enum := range schema.Enum {
			paramInfo.Description += fmt.Sprintf("<br>`%v`", enum)
		}
	}
	paramInfo.Description = strings.ReplaceAll(paramInfo.Description, "\n", "<br>")
	if schema.Description == "" {
		paramInfo.Description = "&nbsp;"
	}
	paramInfo.Description = strings.TrimLeft(paramInfo.Description, "<br>")
	// 参数行
	paramInfo.TableRow = make([]string, 0)
	if paramInfo.Name != "" {
		switch p.ParamTypePosition {
		case ResponseParam, ResponseHeaderParam:
			paramInfo.TableRow = append(paramInfo.TableRow, fmt.Sprintf("|%s  |%s |%s  |\n", paramInfo.Name, paramInfo.Type, paramInfo.Description))
		case CodeParam:
			paramInfo.TableRow = append(paramInfo.TableRow, fmt.Sprintf("|%s  |%s  |%s |%s  |\n", paramInfo.Name, paramInfo.Type, p.MediaType, paramInfo.Description))
		case BodyParam:
			paramInfo.TableRow = append(paramInfo.TableRow, fmt.Sprintf("|%s  |%v  |%s |%s  |\n", paramInfo.Name, paramInfo.Required, paramInfo.Type, paramInfo.Description))
		case QueryRequestParam, HeaderRequestParam, PathRequestParam, CookieRequestParam, FormDataRequestParam, BodyRequestParam:
			paramInfo.TableRow = append(paramInfo.TableRow, fmt.Sprintf("|%s  |%s  |%v |%s  |%s  |\n", paramInfo.Name, p.ParamTypePosition, paramInfo.Required, paramInfo.Type, paramInfo.Description))
		}
	}
	for _, childParam := range paramInfo.ChildParamSchemaInfo {
		paramInfo.TableRow = append(paramInfo.TableRow, childParam.TableRow...)
	}
	return paramInfo
}

func (p *ParamParser) parseParam(schema *SchemaOrItems) (value interface{}, childParams []ParamInfo) {
	childParams = make([]ParamInfo, 0)
	switch schema.Type {
	case "integer", "number":
		value = 0
		if schema.Maximum > 0 {
			value = schema.Minimum
		}
		if schema.Minimum > 0 {
			value = schema.Minimum
		}
	case "string":
		value = ""
	case "array":
		val, params := p.parseParam(schema.Items)
		if p.ParamTypePosition == CodeParam || p.ParamTypePosition == BodyRequestParam {
			break
		}
		childParams = append(childParams, params...)
		value = []interface{}{val}
	case "boolean":
		value = false
	default:
		childSchemas := p.parseObject(schema)
		if p.ParamTypePosition == CodeParam || p.ParamTypePosition == BodyRequestParam {
			break
		}
		ex := make(map[string]interface{})
		for _, childParam := range childSchemas {
			ex[childParam.Name] = childParam.Value
		}
		childParams = append(childParams, childSchemas...)
		value = ex
	}
	if schema.Enum != nil && len(schema.Enum) != 0 {
		value = schema.Enum[0]
	}
	if schema.Default != nil && !reflect.DeepEqual(schema.Default, reflect.Zero(reflect.TypeOf(schema.Default)).Interface()) {
		value = schema.Default
	}
	if schema.Example != nil && !reflect.DeepEqual(schema.Example, reflect.Zero(reflect.TypeOf(schema.Example)).Interface()) {
		value = schema.Example
	}
	return value, childParams
}

func (p *ParamParser) parseObject(info *SchemaOrItems) []ParamInfo {
	info.Type = "object"
	paramSchemaInfoList := make([]ParamInfo, 0)
	// ref
	if info.Ref != "" {
		refDefinitionName := info.Ref[strings.LastIndex(info.Ref, "/")+1:]
		refDefinition, exist := p.definitions[refDefinitionName]
		if !exist && p.components != nil {
			refDefinition, exist = p.components.Schemas[refDefinitionName]
		}
		if !exist {
			return paramSchemaInfoList
		}
		definition := new(SchemaOrItems)
		deserialization(refDefinition, definition)
		if definition.Type != "" {
			info.Type = definition.Type
		}
		paramSchemaInfoList = p.parseObject(definition)
	}
	// allOf
	if info.AllOf != nil {
		infoTypes := make([]string, 0)
		for _, schema := range info.AllOf {
			if schema.Type != "" && schema.Type != "object" {
				newParam := p.newParam("", schema, schema.Required)
				paramSchemaInfoList = append(paramSchemaInfoList, *newParam)
				infoTypes = append(infoTypes, schema.Type)
				continue
			}
			paramInfList := p.parseObject(schema)
			paramSchemaInfoList = append(paramSchemaInfoList, paramInfList...)
		}
		if len(infoTypes) > 0 {
			info.Type = strings.Join(infoTypes, "/")
		}
	}
	// OneOf
	if info.OneOf != nil {
		infoTypes := make([]string, 0)
		for _, schema := range info.OneOf {
			if schema.Type != "" && schema.Type != "object" {
				newParam := p.newParam("", schema, schema.Required)
				paramSchemaInfoList = append(paramSchemaInfoList, *newParam)
				infoTypes = append(infoTypes, schema.Type)
				continue
			}
			paramInfList := p.parseObject(schema)
			paramSchemaInfoList = append(paramSchemaInfoList, paramInfList...)
		}
		if len(infoTypes) > 0 {
			info.Type = strings.Join(infoTypes, "/")
		}
	}
	// AnyOf
	if info.AnyOf != nil {
		infoTypes := make([]string, 0)
		for _, schema := range info.AnyOf {
			if schema.Type != "" && schema.Type != "object" {
				newParam := p.newParam("", schema, schema.Required)
				paramSchemaInfoList = append(paramSchemaInfoList, *newParam)
				infoTypes = append(infoTypes, schema.Type)
				continue
			}
			paramInfList := p.parseObject(schema)
			paramSchemaInfoList = append(paramSchemaInfoList, paramInfList...)
		}
		if len(infoTypes) > 0 {
			info.Type = strings.Join(infoTypes, "/")
		}
	}
	// additionalProperties
	if info.AdditionalProperties != nil {
		randomParams := []string{"additionalProp1", "additionalProp2", "additionalProp3"}
		for _, randomParam := range randomParams {
			newParam := p.newParam(randomParam, info.AdditionalProperties, info.Required)
			paramSchemaInfoList = append(paramSchemaInfoList, *newParam)
		}
	}
	// properties
	if info.Properties != nil {
		// properties
		for param, schema := range info.Properties {
			newParam := p.newParam(param, schema, info.Required)
			paramSchemaInfoList = append(paramSchemaInfoList, *newParam)
		}
	}
	return paramSchemaInfoList
}
